package com.company.hrmadmin.modules.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.company.hrmadmin.common.constant.ErrorEnum;
import com.company.hrmadmin.common.constant.GlobalConstant;
import com.company.hrmadmin.common.exception.BusinessException;
import com.company.hrmadmin.modules.domain.PersonalTax;
import com.company.hrmadmin.modules.domain.Salary;
import com.company.hrmadmin.modules.domain.SalaryRate;
import com.company.hrmadmin.modules.domain.User;
import com.company.hrmadmin.modules.entity.dto.salary.SalaryListDTO;
import com.company.hrmadmin.modules.entity.dto.salary.SalarySortListDTO;
import com.company.hrmadmin.modules.entity.vo.*;
import com.company.hrmadmin.modules.mapper.PersonalTaxMapper;
import com.company.hrmadmin.modules.mapper.SalaryMapper;
import com.company.hrmadmin.modules.mapper.SalaryRateMapper;
import com.company.hrmadmin.modules.service.CompanyService;
import com.company.hrmadmin.modules.service.SalaryService;
import com.company.hrmadmin.modules.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.Month;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author Xmirror
 * @description 针对表【bs_salary(员工薪资表)】的数据库操作Service实现
 * @createDate 2022-04-07 11:28:48
 */
@Service
public class SalaryServiceImpl extends ServiceImpl<SalaryMapper, Salary>
        implements SalaryService {

    @Resource
    private SalaryMapper salaryMapper;
    @Resource
    private SalaryRateMapper salaryRateMapper;
    @Resource
    private PersonalTaxMapper personalTaxMapper;
    @Resource
    private UserService userService;
    @Autowired
    private CompanyService companyService;

    /**
     * 所得税的税率
     */
    protected List<PersonalTax> personalTaxes = null;

    @PostConstruct
    public void init() {
        personalTaxes = personalTaxMapper.selectList(Wrappers.lambdaQuery(PersonalTax.class)
                .orderByAsc(PersonalTax::getId));
    }

    @Override
    public Salary getSalaryByUserId(Integer userId) {
        List<Salary> salaries = salaryMapper.selectList(Wrappers.lambdaQuery(Salary.class)
                .eq(Salary::getUserId, userId).orderByDesc(Salary::getYearTime).orderByDesc(Salary::getMonthTime));
        return salaries.get(0);
    }

    @Override
    public IPage<Salary> getAllSalaryPageDesc(Integer pageSize) {
        IPage<Salary> page = new Page<>();
        // 需要查出同一公司内的员工，该方法是查询所有的员工
        page.setTotal(salaryMapper.selectCount(null));
        page.setSize(pageSize);
        // 如果为空，需要则按照默认
        List<Salary> salaries = salaryMapper.selectList(Wrappers.lambdaQuery(Salary.class)
                .orderByDesc(Salary::getPayrollSalary));
        page.setRecords(salaries);
        return page;
    }

    @Override
    public Integer calAllSalary() {
        List<Salary> salaries = salaryMapper.selectList(null);
        if (salaries == null || salaries.size() == 0) {
            return 0;
        }
        int res = 0;
        for (Salary salary : salaries) {
            res += calSalary(salary);
        }
        return res;
    }

    @Override
    public Integer calAllCost() {
        List<Salary> salaries = salaryMapper.selectList(null);
        if (salaries == null || salaries.size() == 0) {
            return 0;
        }
        int res = 0;
        for (Salary salary : salaries) {
            BigDecimal payrollSalary = salary.getPayrollSalary();
            BigDecimal personalIncomeTax = salary.getPersonalIncomeTax();
            BigDecimal payableSalary = salary.getPayableSalary();
            if (payrollSalary == null || personalIncomeTax == null || payableSalary == null) {
                res += calSalary(salary);
            } else {
                // 用人成本 = 应发工资 + 缴纳总额
                Integer userId = salary.getUserId();
                SalaryRate salaryRate = salaryRateMapper.selectOne(Wrappers
                        .lambdaQuery(SalaryRate.class).eq(SalaryRate::getUserId, userId));
                // 五险一金需要根据 dic_salary_rate 中的数据进行计算，按照应发为基准计算，注意：五险一金中生育和工伤保险个人无需缴纳
                BigDecimal fund = payableSalary.multiply(salaryRate.getCAccumulationFundRate());
                BigDecimal insurance = payableSalary.multiply(salaryRate.getCEndowmentInsuranceRate())
                        .add(payableSalary.multiply(salaryRate.getCInjuryInsuranceRate()))
                        .add(payableSalary.multiply(salaryRate.getCMaternityInsuranceRate()))
                        .add(payableSalary.multiply(salaryRate.getCMedicalInsuranceRate()))
                        .add(payableSalary.multiply(salaryRate.getCUnemploymentInsuranceRate()));
                BigDecimal cost = payableSalary.add(fund).add(insurance);
                Salary temp = new Salary();
                temp.setEmployCost(cost);
                res += salaryMapper.update(temp, Wrappers.lambdaUpdate(Salary.class)
                        .eq(Salary::getUserId, userId));
            }
        }
        return res;
    }

    @Override
    public Integer calSalaryByUserId(Integer userId) {
        LocalDateTime now = LocalDateTime.now();
        int year = now.getYear();
        Month month = now.getMonth();
        Salary salary = salaryMapper.selectOne(Wrappers.lambdaQuery(Salary.class)
                .eq(Salary::getUserId, userId)
                .eq(Salary::getYearTime, year)
                .eq(Salary::getMonthTime, month));
        return calSalary(salary);
    }

    @Override
    public IPage<SalaryDetailVO> getSalaryPage(SalaryListDTO dto) {
        IPage<SalaryDetailVO> page = new Page<>(dto.getPage(), dto.getLimit());
        // 需要查出同一公司内的员工，该方法是查询所有的员工
        List<User> userList = companyService.getCompanyUserList(dto.getUserId());
        // 创造筛选后的ID列表
        List<Integer> ids = new ArrayList<>();
        // 按照姓名模糊查询
        String name = dto.getName();
        if (StringUtils.isNotBlank(name)) {
            for (User user : userList) {
                String regex = "^.*" + dto.getName() + ".*$";
                boolean matches = Pattern.matches(regex, user.getName());
                if (matches) {
                    ids.add(user.getId());
                }
            }
        } else {
            for (User user : userList) {
                ids.add(user.getId());
            }
        }
        page.setTotal(ids.size());
        if (ids.size() == 0) {
            return page;
        }
        // 如果为空，需要则按照默认
        List<Salary> salaries = salaryMapper.selectList(Wrappers.lambdaQuery(Salary.class)
                .in(Salary::getUserId, ids)
                .eq(dto.getYear() != null, Salary::getYearTime, dto.getYear())
                .eq(dto.getMonth() != null, Salary::getMonthTime, dto.getMonth())
                .orderByDesc(Salary::getUserId)
                .last(GlobalConstant.LIMIT + (dto.getPage() - 1) * dto.getLimit()
                        + GlobalConstant.SQL_SPLIT + dto.getLimit()));
        List<SalaryDetailVO> list = new ArrayList<>();
        for (Salary salary : salaries) {
            SalaryDetailVO vo = this.transferSalaryToVO(salary);
            list.add(vo);
        }
        page.setRecords(list);
        return page;
    }

    @Override
    public InsuranceVO getPInsuranceVOBySalaryId(Integer salaryId) {
        Salary salary = salaryMapper.selectById(salaryId);
        if (salary == null) {
            throw new BusinessException(ErrorEnum.SALARY_ID_NULL_ERROR);
        }
        InsuranceVO vo = new InsuranceVO();
        DecimalFormat df = new DecimalFormat("0.00");
        vo.setEndowment(salary.getPEndowmentInsurance() != null ? Double.parseDouble(df.format(salary.getPEndowmentInsurance().doubleValue())) : 0.00);
        vo.setInjury(salary.getPInjuryInsurance() != null ? Double.parseDouble(df.format(salary.getPInjuryInsurance().doubleValue())) : 0.00);
        vo.setMaternity(salary.getPMaternityInsurance() != null ? Double.parseDouble(df.format(salary.getPMaternityInsurance().doubleValue())) : 0.00);
        vo.setMedical(salary.getPMedicalInsurance() != null ? Double.parseDouble(df.format(salary.getPMedicalInsurance().doubleValue())) : 0.00);
        vo.setUnemployment(salary.getPUnemploymentInsurance() != null ? Double.parseDouble(df.format(salary.getPUnemploymentInsurance().doubleValue())) : 0.00);
        vo.setFund(salary.getPAccumulationFund() != null ? Double.parseDouble(df.format(salary.getPAccumulationFund().doubleValue())) : 0.00);
        return vo;
    }

    @Override
    public AllowanceVO getPAllowanceVoBySalaryId(Integer salaryId) {
        Salary salary = salaryMapper.selectById(salaryId);
        if (salary == null) {
            throw new BusinessException(ErrorEnum.SALARY_ID_NULL_ERROR);
        }
        AllowanceVO vo = new AllowanceVO();
        DecimalFormat df = new DecimalFormat("0.00");
        vo.setMeal(salary.getMealAllowance() != null ? Double.parseDouble(df.format(salary.getMealAllowance().doubleValue())) : 0.00);
        vo.setOvertime(salary.getOvertimeAllowance() != null ? Double.parseDouble(df.format(salary.getOvertimeAllowance().doubleValue())) : 0.00);
        vo.setHousing(salary.getHousingAllowance() != null ? Double.parseDouble(df.format(salary.getHousingAllowance().doubleValue())) : 0.00);
        vo.setTransport(salary.getTransportAllowance() != null ? Double.parseDouble(df.format(salary.getTransportAllowance().doubleValue())) : 0.00);
        return vo;
    }

    @Override
    public ChargeVO getPChargeVOBySalaryId(Integer salaryId) {
        Salary salary = salaryMapper.selectById(salaryId);
        if (salary == null) {
            throw new BusinessException(ErrorEnum.SALARY_ID_NULL_ERROR);
        }
        ChargeVO vo = new ChargeVO();
        DecimalFormat df = new DecimalFormat("0.00");
        vo.setLate(salary.getLateCharge() != null ? Double.parseDouble(df.format(salary.getLateCharge().doubleValue())) : 0.00);
        vo.setAbsent(salary.getAbsentCharge() != null ? Double.parseDouble(df.format(salary.getAbsentCharge().doubleValue())) : 0.00);
        return vo;
    }

    @Override
    public Set<Integer> getPSalaryYear(Integer userId) {
        Set<Integer> res = new HashSet<>();
        List<Salary> salaries = salaryMapper.selectList(Wrappers.lambdaQuery(Salary.class)
                .eq(Salary::getUserId, userId));
        if (salaries != null && salaries.size() != 0) {
            for (Salary salary : salaries) {
                res.add(salary.getYearTime());
            }
        }
        return res;
    }

    @Override
    public Set<Integer> getPSalaryMonth(Integer userId) {
        Set<Integer> res = new HashSet<>();
        List<Salary> salaries = salaryMapper.selectList(Wrappers.lambdaQuery(Salary.class)
                .eq(Salary::getUserId, userId));
        if (salaries != null && salaries.size() != 0) {
            for (Salary salary : salaries) {
                res.add(salary.getMonthTime());
            }
        }
        return res;
    }

    @Override
    public IPage<SalaryDetailVO> getSalaryPage(SalaryListDTO dto, SalarySortListDTO sortDto) {
        return null;
    }

    @Override
    public IPage<SalaryDetailVO> getSalaryPageByUserId(SalaryListDTO dto) {
        IPage<SalaryDetailVO> page = new Page<>(dto.getPage(), dto.getLimit());
        // 如果为空，需要则按照默认
        Long count = salaryMapper.selectCount(Wrappers.lambdaQuery(Salary.class)
                .eq(Salary::getUserId, dto.getUserId()));
        List<Salary> salaries = salaryMapper.selectList(Wrappers.lambdaQuery(Salary.class)
                .eq(Salary::getUserId, dto.getUserId())
                .eq(dto.getYear() != null, Salary::getYearTime, dto.getYear())
                .eq(dto.getMonth() != null, Salary::getMonthTime, dto.getMonth())
                .orderByDesc(Salary::getYearTime)
                .orderByDesc(Salary::getMonthTime)
                .last(GlobalConstant.LIMIT + (dto.getPage() - 1) * dto.getLimit()
                        + GlobalConstant.SQL_SPLIT + dto.getLimit()));
        page.setTotal(count);
        List<SalaryDetailVO> list = new ArrayList<>();
        for (Salary salary : salaries) {
            list.add(this.transferSalaryToVO(salary));
        }
        page.setRecords(list);
        return page;
    }

    /**
     * 计算应发工资
     *
     * @param salary salary 对象
     * @return 应发数量
     */
    private BigDecimal calShouldSend(Salary salary) {
        return salary.getBasicSalary().add(salary.getHousingAllowance())
                .add(salary.getOvertimeAllowance()).add(salary.getTransportAllowance())
                .add(salary.getMealAllowance()).subtract(salary.getLateCharge())
                .subtract(salary.getAbsentCharge());
    }

    /**
     * 将 Salary 对象转换为需要的 SalaryVO 对象
     *
     * @param salary 薪资对象
     * @return SalaryDetailVO 对象
     */
    private SalaryDetailVO transferSalaryToVO(Salary salary) {
        this.calSalary(salary);
        SalaryDetailVO vo = new SalaryDetailVO();
        vo.setId(salary.getId());
        User user = userService.getById(salary.getUserId());
        vo.setName(user.getName() == null ? "" : user.getName());
        BigDecimal allowance = salary.getMealAllowance().add(salary.getHousingAllowance())
                .add(salary.getTransportAllowance()).add(salary.getOvertimeAllowance());
        vo.setAllowance(allowance.doubleValue());
        BigDecimal charge = salary.getAbsentCharge().add(salary.getLateCharge());
        vo.setCharge(charge.doubleValue());
        InsuranceFundVO fundInsurance = this.calPInsuranceFund(salary);
        vo.setFund(fundInsurance.getFund() == null ? 0.00 : Double
                .parseDouble(new DecimalFormat("0.00").format(fundInsurance.getFund().doubleValue())));
        vo.setInsurance(fundInsurance.getInsurance() == null ? 0.00 : Double
                .parseDouble(new DecimalFormat("0.00").format(fundInsurance.getInsurance().doubleValue())));
        vo.setPayable(salary.getPayableSalary() == null ? 0.00 : Double
                .parseDouble(new DecimalFormat("0.00").format(salary.getPayableSalary().doubleValue())));
        vo.setPayroll(salary.getPayrollSalary() == null ? 0.00 : Double
                .parseDouble(new DecimalFormat("0.00").format(salary.getPayrollSalary().doubleValue())));
        vo.setTax(salary.getPersonalIncomeTax() == null ? 0.00 : Double
                .parseDouble(new DecimalFormat("0.00").format(salary.getPersonalIncomeTax().doubleValue())));
        // 设置审核员
        vo.setReviewer(null);
        // 设置时间
        vo.setDate(salary.getYearTime() + "年" + salary.getMonthTime() + "月");
        return vo;
    }


    /**
     * 计算薪资，包括个人缴纳部分以及企业缴纳部分、用人成本、应发、实发
     *
     * @param salary Salary 对象
     * @return 计算更新的行数
     */
    private Integer calSalary(Salary salary) {
        // 应发 = 基本工资 + 补贴 - 扣款
        BigDecimal shouldSend = this.calShouldSend(salary);
        // 实发 = 应发工资 - 五险一金 - 税费
        Integer userId = salary.getUserId();
        SalaryRate salaryRate = salaryRateMapper.selectOne(Wrappers
                .lambdaQuery(SalaryRate.class).eq(SalaryRate::getUserId, userId)
                .eq(SalaryRate::getYearTime, salary.getYearTime())
                .eq(SalaryRate::getMonthTime, salary.getMonthTime()));
        // 五险一金需要根据 dic_salary_rate 中的数据进行计算，按照应发为基准计算，注意：五险一金中生育和工伤保险个人无需缴纳
        BigDecimal pAccumulationFund = shouldSend.multiply(salaryRate.getPAccumulationFundRate());
        BigDecimal pEndowmentInsurance = shouldSend.multiply(salaryRate.getPEndowmentInsuranceRate());
        BigDecimal pInjuryInsurance = shouldSend.multiply(salaryRate.getPInjuryInsuranceRate());
        BigDecimal pMaternityInsurance = shouldSend.multiply(salaryRate.getPMaternityInsuranceRate());
        BigDecimal pMedicalInsurance = shouldSend.multiply(salaryRate.getPMedicalInsuranceRate());
        BigDecimal pUnemploymentInsurance = shouldSend.multiply(salaryRate.getPUnemploymentInsuranceRate());
        // 计算社保
        BigDecimal pSocialInsurance = pEndowmentInsurance.add(pInjuryInsurance).add(pMaternityInsurance)
                .add(pMedicalInsurance).add(pUnemploymentInsurance);
        // income = 应发工资 - 五险一金，个人所得税应该在扣除五险一金之后进行计算
        BigDecimal pIncome = shouldSend.subtract(pSocialInsurance).subtract(pAccumulationFund);
        // 应纳税所得额 或 预缴额度，个人所得税 = (实发工资 - 5000 ) * 税率
        BigDecimal pIncomeTaxable = pIncome.compareTo(GlobalConstant.PERSONAL_TAX_START) > 0 ?
                pIncome.subtract(GlobalConstant.PERSONAL_TAX_START) : new BigDecimal("0.00");
        // 返回值为 -1 , 0 , 1 分别表示小于、等于、大于
        BigDecimal personalTax;
        if (pIncomeTaxable.compareTo(new BigDecimal("0.00")) == 0) {
            personalTax = new BigDecimal("0.00");
        } else {
            int flag = 0;
            for (int i = 0; i < personalTaxes.size(); i++) {
                BigDecimal payment = personalTaxes.get(i).getAdvancePayment();
                // 当 payment > incomeTaxable 的时候就返回
                if (payment.compareTo(pIncomeTaxable) >= 0) {
                    flag = i;
                    break;
                }
            }
            // 应纳税额 = 应纳税所得额 × 适用税率 - 速算扣除数
            PersonalTax targetTax = personalTaxes.get(flag);
            // Integer level = targetTax.getId();
            BigDecimal withholdingRate = targetTax.getWithholdingRate();
            BigDecimal quickDeduction = targetTax.getQuickDeduction();
            // System.out.println(withholdingRate.toString() + "  " + quickDeduction.toString());
            personalTax = pIncomeTaxable.multiply(withholdingRate).subtract(quickDeduction);
        }
        // 实发 = income - 税费
        BigDecimal factSend = pIncome.subtract(personalTax);
        // 计算公司用人成本
        BigDecimal cAccumulationFund = shouldSend.multiply(salaryRate.getCAccumulationFundRate());
        BigDecimal cEndowmentInsurance = shouldSend.multiply(salaryRate.getCEndowmentInsuranceRate());
        BigDecimal cInjuryInsurance = shouldSend.multiply(salaryRate.getCInjuryInsuranceRate());
        BigDecimal cMaternityInsurance = shouldSend.multiply(salaryRate.getCMaternityInsuranceRate());
        BigDecimal cMedicalInsurance = shouldSend.multiply(salaryRate.getCMedicalInsuranceRate());
        BigDecimal cUnemploymentInsurance = shouldSend.multiply(salaryRate.getCUnemploymentInsuranceRate());
        BigDecimal employCost = cAccumulationFund.add(cEndowmentInsurance).add(cInjuryInsurance).add(cMaternityInsurance)
                .add(cMedicalInsurance).add(cUnemploymentInsurance).add(shouldSend);
        Salary entity = new Salary();

        // 构建对象
        entity.setPersonalIncomeTax(personalTax);
        entity.setPayableSalary(shouldSend);
        entity.setPayrollSalary(factSend);
        entity.setPAccumulationFund(pAccumulationFund);
        entity.setPEndowmentInsurance(pEndowmentInsurance);
        entity.setPInjuryInsurance(pInjuryInsurance);
        entity.setPMaternityInsurance(pMaternityInsurance);
        entity.setPMedicalInsurance(pMedicalInsurance);
        entity.setPUnemploymentInsurance(pUnemploymentInsurance);
        entity.setCAccumulationFund(cAccumulationFund);
        entity.setCEndowmentInsurance(cEndowmentInsurance);
        entity.setCInjuryInsurance(cInjuryInsurance);
        entity.setCMaternityInsurance(cMaternityInsurance);
        entity.setCMedicalInsurance(cMedicalInsurance);
        entity.setCUnemploymentInsurance(cUnemploymentInsurance);
        entity.setEmployCost(employCost);

        // 更新薪资
        return salaryMapper.update(entity, Wrappers.lambdaUpdate(Salary.class)
                .eq(Salary::getId, salary.getId()));
    }

    private SalaryRate getSalaryRateBySalary(Salary salary) {
        Integer userId = salary.getUserId();
        if (userId == null) {
            throw new BusinessException(ErrorEnum.USER_ID_NULL_ERROR);
        }
        return salaryRateMapper.selectOne(Wrappers
                .lambdaQuery(SalaryRate.class).eq(SalaryRate::getUserId, userId)
                .eq(SalaryRate::getYearTime, salary.getYearTime())
                .eq(SalaryRate::getMonthTime, salary.getMonthTime()));
    }

    private InsuranceFundVO calPInsuranceFund(Salary salary) {
        // 应发 = 基本工资 + 补贴 - 扣款
        BigDecimal shouldSend = this.calShouldSend(salary);
        // 实发 = 应发工资 - 五险一金 - 税费
        SalaryRate salaryRate = this.getSalaryRateBySalary(salary);
        // 五险一金需要根据 dic_salary_rate 中的数据进行计算，按照应发为基准计算，注意：五险一金中生育和工伤保险个人无需缴纳
        BigDecimal fund = shouldSend.multiply(salaryRate.getPAccumulationFundRate());
        BigDecimal insurance = shouldSend.multiply(salaryRate.getPEndowmentInsuranceRate())
                .add(shouldSend.multiply(salaryRate.getPInjuryInsuranceRate()))
                .add(shouldSend.multiply(salaryRate.getPMaternityInsuranceRate()))
                .add(shouldSend.multiply(salaryRate.getPMedicalInsuranceRate()))
                .add(shouldSend.multiply(salaryRate.getPUnemploymentInsuranceRate()));
        InsuranceFundVO vo = new InsuranceFundVO();
        vo.setInsurance(insurance);
        vo.setFund(fund);
        return vo;
    }
}
